/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"
#include "test_helper.h"
#include "include/coretypes/dyn_objects.h"
#include "plugins/ecmascript/runtime/linked_hash_table-inl.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "include/coretypes/tagged_value.h"
#include "include/runtime.h"
#include "include/runtime_options.h"
#include "plugins/ecmascript/runtime/tagged_hash_table-inl.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_map.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_iterator.h"
#include "plugins/ecmascript/runtime/js_map_iterator.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

namespace ark::test {
class JSMapTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {};

    JSMap *CreateMap()
    {
        ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
        JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
        JSHandle<JSTaggedValue> constructor = env->GetMapFunction();
        JSHandle<JSMap> map =
            JSHandle<JSMap>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), constructor));
        JSHandle<LinkedHashMap> hashMap = LinkedHashMap::Create(thread_);
        map->SetLinkedMap(thread_, hashMap);
        return *map;
    }

private:
    PandaVM *instance_ {nullptr};
    ecmascript::EcmaHandleScope *scope_ {nullptr};
};

TEST_F(JSMapTest, MapCreate)
{
    JSMap *map = CreateMap();
    EXPECT_TRUE(map != nullptr);
}

TEST_F(JSMapTest, AddAndHas)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // create js_map
    JSHandle<JSMap> map(thread_, CreateMap());

    JSHandle<JSTaggedValue> key(factory->NewFromString("key"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));
    JSMap::Set(thread_, map, key, value);
    int hash = LinkedHash::Hash(key.GetTaggedValue());
    EXPECT_TRUE(map->Has(key.GetTaggedValue(), hash));
}

TEST_F(JSMapTest, DeleteAndGet)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // create js_map
    JSHandle<JSMap> map(thread_, CreateMap());

    // add 40 keys
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    char keyArray[] = "key0";
    // NOLINTNEXTLINE(readability-magic-numbers)
    for (int i = 0; i < 40; i++) {
        keyArray[3] = '1' + i;
        JSHandle<JSTaggedValue> key(factory->NewFromString(keyArray));
        JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(i));
        JSMap::Set(thread_, map, key, value);
        int hash = LinkedHash::Hash(key.GetTaggedValue());
        EXPECT_TRUE(map->Has(key.GetTaggedValue(), hash));
    }
    EXPECT_EQ(map->GetSize(), 40);
    // whether js_map has delete key
    keyArray[3] = '1' + 8;
    JSHandle<JSTaggedValue> deleteKey(factory->NewFromString(keyArray));
    EXPECT_EQ(map->GetValue(8), JSTaggedValue(8));
    JSMap::Delete(thread_, map, deleteKey);
    int hash = LinkedHash::Hash(deleteKey.GetTaggedValue());
    EXPECT_FALSE(map->Has(deleteKey.GetTaggedValue(), hash));
    EXPECT_EQ(map->GetSize(), 39);
}

TEST_F(JSMapTest, Iterator)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<JSMap> map(thread_, CreateMap());
    for (int i = 0; i < 5; i++) {
        JSHandle<JSTaggedValue> key(thread_, JSTaggedValue(i));
        // NOLINTNEXTLINE(readability-magic-numbers)
        JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(i + 10));
        JSMap::Set(thread_, map, key, value);
    }

    JSHandle<JSTaggedValue> keyIter(factory->NewJSMapIterator(map, IterationKind::KEY));
    JSHandle<JSTaggedValue> valueIter(factory->NewJSMapIterator(map, IterationKind::VALUE));
    JSHandle<JSTaggedValue> iter(factory->NewJSMapIterator(map, IterationKind::KEY_AND_VALUE));

    JSHandle<JSTaggedValue> indexKey(thread_, JSTaggedValue(0));
    JSHandle<JSTaggedValue> elementKey(thread_, JSTaggedValue(1));

    JSHandle<JSTaggedValue> keyResult0 = JSIterator::IteratorStep(thread_, keyIter);
    JSHandle<JSTaggedValue> valueResult0 = JSIterator::IteratorStep(thread_, valueIter);
    JSHandle<JSTaggedValue> result0 = JSIterator::IteratorStep(thread_, iter);

    EXPECT_EQ(0, JSIterator::IteratorValue(thread_, keyResult0)->GetInt());
    EXPECT_EQ(10, JSIterator::IteratorValue(thread_, valueResult0)->GetInt());
    JSHandle<JSTaggedValue> result0Handle = JSIterator::IteratorValue(thread_, result0);
    EXPECT_EQ(0, JSObject::GetProperty(thread_, result0Handle, indexKey).GetValue()->GetInt());
    EXPECT_EQ(10, JSObject::GetProperty(thread_, result0Handle, elementKey).GetValue()->GetInt());

    JSHandle<JSTaggedValue> keyResult1 = JSIterator::IteratorStep(thread_, keyIter);
    EXPECT_EQ(1, JSIterator::IteratorValue(thread_, keyResult1)->GetInt());
    for (int i = 0; i < 3; i++) {
        JSHandle<JSTaggedValue> key(thread_, JSTaggedValue(i));
        JSMap::Delete(thread_, map, key);
    }
    JSHandle<JSTaggedValue> keyResult2 = JSIterator::IteratorStep(thread_, keyIter);
    EXPECT_EQ(3, JSIterator::IteratorValue(thread_, keyResult2)->GetInt());
    JSHandle<JSTaggedValue> keyResult3 = JSIterator::IteratorStep(thread_, keyIter);
    EXPECT_EQ(4, JSIterator::IteratorValue(thread_, keyResult3)->GetInt());
    JSHandle<JSTaggedValue> key(thread_, JSTaggedValue(5));
    JSMap::Set(thread_, map, key, key);
    JSHandle<JSTaggedValue> keyResult4 = JSIterator::IteratorStep(thread_, keyIter);

    EXPECT_EQ(5, JSIterator::IteratorValue(thread_, keyResult4)->GetInt());
    JSHandle<JSTaggedValue> keyResult5 = JSIterator::IteratorStep(thread_, keyIter);
    EXPECT_EQ(JSTaggedValue::False(), keyResult5.GetTaggedValue());
}

}  // namespace ark::test
